
package com.barney.support.web;

import java.beans.PropertyDescriptor;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.beanutils.PropertyUtils;

import com.barney.support.domain.Search;

/**
 * 웹 검색
 * 
 * @author Barney Kim
 */
public class WebSearch implements Search {
	
	private static final int DEFAULT_PAGE_SIZE = 10;
	
	private int page;
	
	private int pageSize;
	
	private Collection<String> paramCandidatePropertyDescriptorNames;
	
	private static final Set<String> EXCLUDED_PROPERTIES;

	static {
		EXCLUDED_PROPERTIES = Collections.unmodifiableSet(new HashSet<String>() {
			private static final long serialVersionUID = 1L;
			{
				add("class");
				add("offset");
				add("page");
				add("pageSize");
			}
		});
	}

	
	public WebSearch() {
		page = 1;
		pageSize = DEFAULT_PAGE_SIZE;
		
		populateParamCandidatePropertyDescriptorNames();
	}
	
	/**
	 * 페이지 번호를 반환한다. 설정된 값이 1보다 작다면 기본 값 1을 반환한다.
	 */
	public int getPage() {
		if(page < 1) {
			page = 1;
		}
		return page;
	}
	
	public void setPage(int page) {
		this.page = page;
	}

	/**
	 * 페이지 크기를 반환한다. 설정된 값이 1보다 작다면 기본 값 10을 반환한다.
	 */
	public int getPageSize() {
		if(pageSize < 1) {
			pageSize = DEFAULT_PAGE_SIZE;
		}
		return pageSize;
	}

	public void setPageSize(int pageSize) {
		this.pageSize = pageSize;
	}
	
	public int getOffset() {
		return getPageSize() * (getPage() - 1);
	}
	
	/**
	 * prefixURI가 없고 페이지 번호는 현재 페이지 번호를 사용하는 URL을 반환한다.
	 * 
	 * @see #toUrl(String, int)
	 */
	public String toUrl() {
		return toUrl(null, getPage());
	}
	
	/**
	 * prefixUri를 사용해 현재 페이지 번호를 사용하는 URL을 반환한다.
	 * 
	 * @param prefixUri
	 * @see #toUrl(String, int)
	 */
	public String toUrl(String prefixUri) {
		return toUrl(prefixUri, getPage());
	}

	/**
	 * 현재 검색 값들을 매개변수로 갖는 URL을 생성한다.
	 * 예를 들어 "/dir/some"이면 "/dir/some?page=1.." 형태의 URL이 생성된다.
	 *
	 * @param prefixURI 매개변수의 앞에 올 URI
	 * @param page 페이지 번호
	 */
	public String toUrl(String prefixUri, int page) {
		if(prefixUri == null) {
			prefixUri = "";
		}

		String paramConjunctionChar = prefixUri.contains("?") ? "&" : "?";

		StringBuffer urlBuffer = new StringBuffer();
		urlBuffer.append(prefixUri).append(paramConjunctionChar).append("page=").append(page);

		populatePropertyParams(urlBuffer);
		return urlBuffer.toString();
	}
	
	private void populatePropertyParams(StringBuffer urlBuffer) {
		for (String propertyName : paramCandidatePropertyDescriptorNames) {

			Object property = null;
			try {
				property = PropertyUtils.getProperty(this, propertyName);
			} catch (Exception e) {
				e.printStackTrace();
			}
			if(property == null) {
				continue;
			}

			if (property.getClass().isArray()) {
				addArrayParameter(urlBuffer, propertyName, property);
			} else if (property instanceof Collection) {
				addCollectionParameter(urlBuffer, propertyName, property);
			} else {
				addParameter(urlBuffer, propertyName, property);
			}

		}
	}

	private void addCollectionParameter(StringBuffer urlBuffer, String propertyName, Object property) {
		@SuppressWarnings("unchecked")
		Collection<? extends Object> collection = (Collection<? extends Object>) property;
		for (Object value : collection) {
			addParameter(urlBuffer, propertyName, value);
		}
	}

	private void addArrayParameter(StringBuffer urlBuffer, String propertyName, Object property) {
		for (Object value : (Object[]) property) {
			addParameter(urlBuffer, propertyName, value);
		}
	}

	private void addParameter(StringBuffer urlBuffer, String propertyName, Object value) {
		try {
			urlBuffer.append("&").append(propertyName).append("=").
				append(URLEncoder.encode(value.toString(), "UTF-8"));
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 페이징 URL의 파라미터가 되는 프라퍼티 이름 목록을 객체 생성시 미리 만들어 둔다.
	 */
	private void populateParamCandidatePropertyDescriptorNames() {
		PropertyDescriptor[] propertyDescriptors = PropertyUtils.getPropertyDescriptors(this.getClass());
		Set<String> exclusivePropertyNames = getExclusivePropertyNames();

		paramCandidatePropertyDescriptorNames = new HashSet<String>();

		for (PropertyDescriptor descriptor : propertyDescriptors) {
			String propertyName = descriptor.getName();

			boolean excluded = exclusivePropertyNames.contains(propertyName);
			if (!excluded) {
				paramCandidatePropertyDescriptorNames.add(propertyName);
			}
		}
	}

	/**
	 * 매개변수 프라퍼티 이름 목록을 반환한다.
	 */
	protected final Collection<String> getParamCandidatePropertyDescriptorNames() {
		return paramCandidatePropertyDescriptorNames;
	}

	/**
	 * 검색 URL에서 제외할 프라퍼티 이름 목록 하위 클래스에서는 항상 super.getExclusivePropertyNames()의
	 * 값에, 하위클래스 전용 값을 더해서 리턴해야 한다.
	 */
	protected Set<String> getExclusivePropertyNames() {
		return EXCLUDED_PROPERTIES;
	}
}
